﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

using JoyServer.Events;

namespace JoyServer.Net
{
	/// <summary>
	/// crossdomain.xml 形式の、アクセス許可情報を送信するためのサーバ
	/// デフォルトポート : 843
	/// </summary>
	class PolicyFileServer : EventDispatcher
	{
		/// <summary>
		/// ポリシーファイルのデフォルトファイル名
		/// </summary>
		private const String POLICY_FILE_PATH = "crossdomain.xml";

		/// <summary>
		/// 待ち受けポートのデフォルト値
		/// </summary>
		public const int DEFAULT_PORT = 843;

		/// <summary>
		/// 待ち受けポート
		/// </summary>
		private int port;

		/// <summary>
		/// ポリシーファイルの内容
		/// </summary>
		private String policyString;

		/// <summary>
		/// TCP 待ち受けオブジェクト
		/// </summary>
		private TcpListener tcpListener;

		/// <summary>
		/// 接続してきたクライアントの情報
		/// </summary>
		private List<ClientConnection> connections;

		/// <summary>
		/// 待ち受けスレッド
		/// ブロッキングモードで動作するので、
		/// 別スレッドで起動
		/// </summary>
		private Thread listenerThread;

		/// <summary>
		/// サーバ停止フラグ
		/// </summary>
		private bool isStop;
		
		// --------------------------------------------------------------------

		/// <summary>
		/// コンストラクタ
		/// </summary>
		public PolicyFileServer(int port)
		{
			this.port = port;
			this.policyString = "";
			this.isStop = false;
			this.connections = new List<ClientConnection>();
		}

		/// <summary>
		/// サーバ開始
		/// </summary>
		public void start()
		{
			// 既に開始されている時はそのまま終了
			if (isStarted())
				return;

			isStop = false;

			// ポリシーファイルの内容を取得する
			policyString = readPolicyFile();
			if (policyString == "")
			{
				dispatchNotifyEvent("crossdomain.xml ファイルの読み取りに失敗しました。" +
									"Exe と同じディレクトリに、 crossdomain.xml ファイルを" +
									"配置してください。");
				return;
			}

			// サーバ待ち受けスレッドを開始する
			startListenerThread();
		}

		/// <summary>
		/// サーバ停止
		/// </summary>
		public void stop()
		{
			isStop = true;

			// クラアントとの接続を全て閉じる
			closeAllClients();

			// 待ち受けを停止する
			stopListenerThread();

			dispatchNotifyEvent("サーバが停止されました。");
		}



		/// <summary>
		/// ポリシーファイルの内容を取得する
		/// </summary>
		/// <returns>ポリシーファイルの内容が入った文字列</returns>
		private String readPolicyFile()
		{
			if (!File.Exists(POLICY_FILE_PATH))
				return "";

			return File.ReadAllText(POLICY_FILE_PATH);
		}

		/// <summary>
		/// 待ち受け用スレッドが開始されているか確認
		/// </summary>
		/// <returns>開始されている場合 true</returns>
		private bool isStarted()
		{
			if (listenerThread != null)
			{
				return true;
			}
			return false;
		}

		/// <summary>
		/// サーバの待ち受け用スレッドを開始する
		/// </summary>
		private void startListenerThread()
		{
			ThreadStart threadStart = new ThreadStart(listen);
			listenerThread = new Thread(threadStart);
			listenerThread.Start();
		}

		/// <summary>
		/// サーバの待ち受け用スレッドを停止する
		/// </summary>
		private void stopListenerThread()
		{
			// 待ち受けを停止する
			if (tcpListener != null)
				tcpListener.Stop();

			// 待ち受けスレッドを停止する
			if (listenerThread != null)
			{
				listenerThread.Join();
				listenerThread = null;
			}
		}

		/// <summary>
		/// 接続中の全てのクライアントを切断する
		/// </summary>
		private void closeAllClients()
		{
			foreach (ClientConnection connection in connections)
			{
				connection.close();
			}
			connections.Clear();
		}

		/// <summary>
		/// メッセージ通知用イベントの発行
		/// </summary>
		/// <param name="message"></param>
		private void dispatchNotifyEvent(String message)
		{
			ServerEvent e;
			e = new ServerEvent(this, ServerEvent.NOTIFY);
			e.message = "PolicyFileServer: " + message;
			dispatchEvent(e);
		}

		/// <summary>
		/// サーバ待ち受けループ
		/// 別スレッドで動作する
		/// </summary>
		private void listen()
		{
			dispatchNotifyEvent("サーバが開始されました。");

			try
			{
				tcpListener = new TcpListener(IPAddress.Loopback, port);
				tcpListener.Start();

				dispatchNotifyEvent("接続を待っています。");
				while (!isStop)
				{
					checkPendingAndAccept();
					Thread.Sleep(10);
				}
			}
			catch (SocketException e)
			{
				dispatchNotifyEvent("エラー (SocketException) : " + e.ToString());
			}
			finally
			{
				tcpListener.Stop();
			}
		}

		/// <summary>
		/// クライアントの接続待ちと接続受け入れ
		/// </summary>
		private void checkPendingAndAccept()
		{
			if (tcpListener.Pending())
			{
				TcpClient client = tcpListener.AcceptTcpClient();
				ClientConnection connection = new ClientConnection();
				connection.addEventListener(ClientEvent.DATA_RECEIVE, onClientDataReceive);
				connection.addEventListener(ClientEvent.CLOSE, onClientClose);
				connection.addEventListener(ClientEvent.SENT, onClientSent);
				connection.start(client);
				connections.Add(connection);

				if (client.Client.Connected == true)
					dispatchNotifyEvent("クライアントが接続しました。 : " + 
										client.Client.RemoteEndPoint.ToString());
				
				dispatchNotifyEvent("現在の接続数: " + connections.Count);
			}
		}

		/// <summary>
		/// ClientConnection オブジェクトから送信される
		/// データ受信イベント
		/// </summary>
		/// <param name="e"></param>
		private void onClientDataReceive(Event e)
		{
			ClientEvent clientEvent = e as ClientEvent;
			if (clientEvent == null)
				return;

			uint length = clientEvent.data.length;
			string message = clientEvent.data.readUTFBytes(length);
			message = message.Trim();
			//Debug.WriteLine("data receive (" + length.ToString() + ") : " + message);

			int iCompare = string.Compare(message, "<policy-file-request/>");
			if (iCompare == 0)
			{
				dispatchNotifyEvent("ポリシーファイルを送信します。");
				ByteArray data = new ByteArray();
				data.writeUTFBytes(policyString);
				clientEvent.connection.send(data);
			}
		}

		/// <summary>
		/// ClientConnection オブジェクトから送信される
		/// クライアント切断完了イベント
		/// </summary>
		/// <param name="e"></param>
		private void onClientClose(Event e)
		{
			ClientEvent clientEvent = e as ClientEvent;
			if (clientEvent == null)
				return;

			ClientConnection connection = clientEvent.connection;
			connections.Remove(connection);

			dispatchNotifyEvent("クライアントの接続を閉じました。 : " +
								connection.endPointString);
			dispatchNotifyEvent("現在の接続数: " + connections.Count);
		}

		/// <summary>
		/// ClientConnection オブジェクトから送信される
		/// データ送信完了イベント
		/// </summary>
		/// <param name="e"></param>
		private void onClientSent(Event e)
		{
			ClientEvent clientEvent = e as ClientEvent;
			if (clientEvent == null)
				return;

			dispatchNotifyEvent("ポリシーファイルを送信しました。");
			clientEvent.connection.close();
		}
	}
}
